package com.liruo.learn.spring.mvc.enumconverter.gson;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.liruo.learn.spring.mvc.enumconverter.SourceEnum;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author:liruo
 * @Date:2023-02-20-20:13:33
 * @Desc
 */
public class EnumTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        Class<? super T> rawType = typeToken.getRawType();
        if (SourceEnum.class.isAssignableFrom(rawType) && rawType != Enum.class && rawType.isEnum()) {
            return new EnumTypeAdapter(rawType);
        } else {
            return null;
        }
    }
    public  static class EnumTypeAdapter<S, E extends SourceEnum<S>> extends TypeAdapter<E>{
        private final Class<E> enumClazz;
        private final boolean isInt;
        private Map<Integer, E> ordinalMap = null;
        private Map<String, E> nameMap = null;

        public EnumTypeAdapter(Class<E> clazz) {
            this.isInt = clazz.getEnumConstants()[0].getSource() instanceof Integer;
            if(!this.isInt){
                if(!(clazz.getEnumConstants()[0].getSource() instanceof String)){
                    throw new RuntimeException("source must be Integer or String");
                }
            }
            this.enumClazz = clazz;
        }

        @Override
        public void write(JsonWriter jsonWriter, E enumToConvert) throws IOException {
            S source = enumToConvert.getSource();
            if(enumToConvert == null){
                jsonWriter.nullValue();
            }else{
                if(isInt){
                    jsonWriter.value((Integer) source);
                }else{
                    jsonWriter.value((String) source);
                }
            }
        }

        @Override
        public E read(JsonReader jsonReader) throws IOException {
            JsonToken peek = jsonReader.peek();
            if (peek != JsonToken.NULL) {
                Object source = null;
                try {
                    if (isInt) {
                        if(peek == JsonToken.NUMBER){
                            source = jsonReader.nextInt();
                        }else if(peek == JsonToken.STRING){
                            try {
                                source = Integer.parseInt(jsonReader.nextString());
                            }catch (Exception e){
                                throw new RuntimeException("need int value  give string but parse fail err=" + e.getMessage());
                            }
                        }else{
                            throw new RuntimeException("need int value not " + peek.name());
                        }
                    } else {
                        source = jsonReader.nextString();
                    }
                    for (E enumeration : enumClazz.getEnumConstants()) {
                        if (enumeration.getSource().equals(source)) {
                            return enumeration;
                        }
                    }
                } catch (IOException | RuntimeException e) {
                    if(peek == JsonToken.STRING){
                        E[] enumConstants = enumClazz.getEnumConstants();
                        for (E enumeration : enumConstants) {
                            if(nameMap == null){
                                nameMap = new HashMap<>(enumConstants.length);
                            }
                            nameMap.put(((Enum)enumeration).name(), enumeration);
                            if(ordinalMap == null){
                                ordinalMap = new HashMap<>(enumConstants.length);
                            }
                            ordinalMap.put(((Enum)enumeration).ordinal(), enumeration);
                        }
                    }
                    throw new RuntimeException(e);
                }
            }
            jsonReader.nextNull();
            return null;
        }
    }
}
